// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package net

import (
	"context"
	"internal/itoa"
	"net/netip"
	"syscall"
)

// BUG（mikio）：在计划9中，UDPConn的ReadMsgUDP和
// WriteMsgUDP方法没有实现。

// 错误（mikio）：在Windows上，UDPConn的文件方法没有实现。

// 错误（mikio）：在JS上，与UDPConn相关的方法和函数没有实现。

// UDPAddr表示UDP端点的地址。
type UDPAddr struct {
	IP   IP
	Port int
	Zone string // IPv6作用域寻址区
}

// AddrPort将UDPADR a作为netip返回。阿德波特。
// 
// 如果一个端口不适合uint16，它会被自动截断。
// 
// 如果a为零，则返回零值。
func (a *UDPAddr) AddrPort() netip.AddrPort {
	if a == nil {
		return netip.AddrPort{}
	}
	na, _ := netip.AddrFromSlice(a.IP)
	na = na.WithZone(a.Zone)
	return netip.AddrPortFrom(na, uint16(a.Port))
}

// 网络返回地址的网络名“udp”。
func (a *UDPAddr) Network() string { return "udp" }

func (a *UDPAddr) String() string {
	if a == nil {
		return "<nil>"
	}
	ip := ipEmptyString(a.IP)
	if a.Zone != "" {
		return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port))
	}
	return JoinHostPort(ip, itoa.Itoa(a.Port))
}

func (a *UDPAddr) isWildcard() bool {
	if a == nil || a.IP == nil {
		return true
	}
	return a.IP.IsUnspecified()
}

func (a *UDPAddr) opAddr() Addr {
	if a == nil {
		return nil
	}
	return a
}

// ResolveUDPAddr返回UDP端点的地址。
// 
// 网络必须是UDP网络名。
// 
// 如果address参数中的主机不是文字IP地址或
// 端口不是文字端口号，ResolveUDPAddr将
// 地址解析为UDP端点的地址。
// 否则，它会将地址解析为一对文字IP地址
// 和端口号。
// address参数可以使用主机名，但不建议使用
// 因为它最多会返回主机名的一个
// IP地址。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
	switch network {
	case "udp", "udp4", "udp6":
	case "": // Go 1.0未记录行为的提示通配符
		network = "udp"
	default:
		return nil, UnknownNetworkError(network)
	}
	addrs, err := DefaultResolver.internetAddrList(context.Background(), network, address)
	if err != nil {
		return nil, err
	}
	return addrs.forResolve(network, address).(*UDPAddr), nil
}

// UDPAddrFromAddrPort将addr作为UDPAddr返回。如果地址是。IsValid（）为false，
// 则返回的UDPAddr将包含一个nil IP字段，表示
// 地址族不可知的未指定地址。
func UDPAddrFromAddrPort(addr netip.AddrPort) *UDPAddr {
	return &UDPAddr{
		IP:   addr.Addr().AsSlice(),
		Zone: addr.Addr().Zone(),
		Port: int(addr.Port()),
	}
}

// AddrPortedPaddr是一个netip。满足Addr接口的基于AddrPort的UDP地址。
type addrPortUDPAddr struct {
	netip.AddrPort
}

func (addrPortUDPAddr) Network() string { return "udp" }

// UDPConn是用于UDP网络连接的Conn和PacketConn接口
// 的实现。
type UDPConn struct {
	conn
}

// SyscallConn返回原始网络连接。
// 这实现了系统调用。控制接口。
func (c *UDPConn) SyscallConn() (syscall.RawConn, error) {
	if !c.ok() {
		return nil, syscall.EINVAL
	}
	return newRawConn(c.fd)
}

// ReadFromUDP的行为类似于ReadFrom，但返回一个UDPADD。
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) {
	// 此函数旨在允许调用方控制返回的*UDPAddr的生存期
	// 从而防止分配。
	// 参见https:
	// 真正的工作是通过下面的readFromUDP完成的。
	return c.readFromUDP(b, &UDPAddr{})
}

// readFromUDP实现readFromUDP。
func (c *UDPConn) readFromUDP(b []byte, addr *UDPAddr) (int, *UDPAddr, error) {
	if !c.ok() {
		return 0, nil, syscall.EINVAL
	}
	n, addr, err := c.readFrom(b, addr)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return n, addr, err
}

// ReadFrom实现PacketConn ReadFrom方法。
func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) {
	n, addr, err := c.readFromUDP(b, &UDPAddr{})
	if addr == nil {
		// 返回地址（nil），而不是地址（*UDPConn（nil））。
		return n, nil, err
	}
	return n, addr, err
}

// ReadFromUDPAddrPort的行为类似于ReadFrom，但返回一个netip。阿德波特。
func (c *UDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
	if !c.ok() {
		return 0, netip.AddrPort{}, syscall.EINVAL
	}
	n, addr, err = c.readFromAddrPort(b)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return n, addr, err
}

// ReadMsgUDP从c读取一条消息，将有效负载复制到b中，并将相关带外数据复制到oob中。它返回复制到b中的
// 字节数、复制到oob中的字节数、消息上设置的标志
// 以及消息的源地址。
// 
// 戈朗的包裹。org/x/net/ipv4和golang。org/x/net/ipv6可以是
// 用于操作oob中的IP级别套接字选项。
func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error) {
	var ap netip.AddrPort
	n, oobn, flags, ap, err = c.ReadMsgUDPAddrPort(b, oob)
	if ap.IsValid() {
		addr = UDPAddrFromAddrPort(ap)
	}
	return
}

// ReadMsgUDPAddrPort类似于ReadMsgUDP，但返回一个netip。AddrPort而不是UDPAddr。
func (c *UDPConn) ReadMsgUDPAddrPort(b, oob []byte) (n, oobn, flags int, addr netip.AddrPort, err error) {
	if !c.ok() {
		return 0, 0, 0, netip.AddrPort{}, syscall.EINVAL
	}
	n, oobn, flags, addr, err = c.readMsg(b, oob)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return
}

// WriteToUDP的行为类似于WriteTo，但使用UDPADD。
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	n, err := c.writeTo(b, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err}
	}
	return n, err
}

// WriteToUDPAddrPort的行为类似于WriteTo，但需要一个netip。阿德波特。
func (c *UDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	n, err := c.writeToAddrPort(b, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addrPortUDPAddr{addr}, Err: err}
	}
	return n, err
}

// WriteTo实现PacketConn WriteTo方法。
func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	a, ok := addr.(*UDPAddr)
	if !ok {
		return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr, Err: syscall.EINVAL}
	}
	n, err := c.writeTo(b, a)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: a.opAddr(), Err: err}
	}
	return n, err
}

// WriteMsgUDP在未连接c时通过c向地址写入消息，或在连接c时通过
// 向c的远程地址写入消息（在这种情况下，地址必须是
// nil）。有效载荷从b复制，相关带外
// 数据从oob复制。它返回有效负载和
// 写入的带外字节数。
// 
// 戈朗的包裹。org/x/net/ipv4和golang。org/x/net/ipv6可以是
// 用于操作oob中的IP级别套接字选项。
func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) {
	if !c.ok() {
		return 0, 0, syscall.EINVAL
	}
	n, oobn, err = c.writeMsg(b, oob, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err}
	}
	return
}

// writemsgudpardport类似于writemsgudpudp，但需要一个netip。AddrPort而不是UDPAddr。
func (c *UDPConn) WriteMsgUDPAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error) {
	if !c.ok() {
		return 0, 0, syscall.EINVAL
	}
	n, oobn, err = c.writeMsgAddrPort(b, oob, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addrPortUDPAddr{addr}, Err: err}
	}
	return
}

func newUDPConn(fd *netFD) *UDPConn { return &UDPConn{conn{fd}} }

// DialUDP的作用类似于UDP网络的拨号。
// 
// 网络必须是UDP网络名；有关详细信息，请参阅func Dial。
// 
// 如果laddr为零，则自动选择本地地址。
// 如果raddr的IP字段为零或未指定的IP地址，则
// 假设为本地系统。
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) {
	switch network {
	case "udp", "udp4", "udp6":
	default:
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(network)}
	}
	if raddr == nil {
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress}
	}
	sd := &sysDialer{network: network, address: raddr.String()}
	c, err := sd.dialUDP(context.Background(), laddr, raddr)
	if err != nil {
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err}
	}
	return c, nil
}

// ListenUDP的作用类似于UDP网络的ListenPacket。
// 
// 网络必须是UDP网络名；有关详细信息，请参阅func Dial。
// 
// 如果laddr的IP字段为零或未指定的IP地址，
// ListenUDP将侦听本地系统的所有可用IP地址
// 除了多播IP地址。
// 如果laddr的端口字段为0，则自动选择端口号
// 。
func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error) {
	switch network {
	case "udp", "udp4", "udp6":
	default:
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(network)}
	}
	if laddr == nil {
		laddr = &UDPAddr{}
	}
	sl := &sysListener{network: network, address: laddr.String()}
	c, err := sl.listenUDP(context.Background(), laddr)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: laddr.opAddr(), Err: err}
	}
	return c, nil
}

// ListenMulticastUDP与UDP网络的ListenPackage类似，但
// 在特定网络接口上获取组地址。
// 
// 网络必须是UDP网络名；有关详细信息，请参阅func Dial。
// 
// ListenMulticastUDP监听
// 本地系统的所有可用IP地址，包括组、多播IP地址。
// 如果ifi为零，ListenMulticastUDP使用系统分配的
// 多播接口，但不建议这样做，因为
// 分配取决于平台，有时可能需要
// 路由配置。
// 如果gaddr的端口字段为0，则会自动选择端口号
// 。
// 
// ListenMulticastUDP只是为了方便简单的小型应用程序。有戈朗。org/x/net/ipv4和
// golang。org/x/net/ipv6通用包。
// 
// 请注意，ListenMulticastUDP将IPPROTO_IP下的IP_MULTICAST_LOOP socket选项
// 设置为0，以禁用多播数据包的环回。
func ListenMulticastUDP(network string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error) {
	switch network {
	case "udp", "udp4", "udp6":
	default:
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: UnknownNetworkError(network)}
	}
	if gaddr == nil || gaddr.IP == nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: errMissingAddress}
	}
	sl := &sysListener{network: network, address: gaddr.String()}
	c, err := sl.listenMulticastUDP(context.Background(), ifi, gaddr)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: err}
	}
	return c, nil
}
